package com.luqm.mall.mallsearch.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.lqm.common.to.es.SkuEsModel;
import com.lqm.common.utils.R;
import com.luqm.mall.mallsearch.config.MallElasticConfig;
import com.luqm.mall.mallsearch.constant.EsConstant;
import com.luqm.mall.mallsearch.feign.ProductFeignService;
import com.luqm.mall.mallsearch.service.MallSearchService;
import com.luqm.mall.mallsearch.vo.AttrResponseVo;
import com.luqm.mall.mallsearch.vo.BrandVo;
import com.luqm.mall.mallsearch.vo.EsSearchResult;
import com.luqm.mall.mallsearch.vo.SearchParam;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Autowired
    private RestHighLevelClient esClient;
    @Autowired
    private ProductFeignService productFeignService;

    @Override
    public EsSearchResult search(SearchParam param) {
        //检索请求
        SearchRequest searchRequest=buildSearchRequest(param);
        EsSearchResult result=null;
        try {
            //执行请求
            SearchResponse response = esClient.search(searchRequest, MallElasticConfig.COMMON_OPTIONS);
            //封装相应结果
            result=buildSearchResult(response,param);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private EsSearchResult buildSearchResult(SearchResponse response, SearchParam param) {
        EsSearchResult result=new EsSearchResult();
        SearchHit[] hits = response.getHits().getHits();
        if(hits!=null && hits.length>0){
            List<SkuEsModel> skuEsModels = Arrays.stream(hits).map((item) -> {
                String source = item.getSourceAsString();
                SkuEsModel model = JSON.parseObject(source, new TypeReference<SkuEsModel>() {
                });
                Map<String, HighlightField> highlightFields = item.getHighlightFields();
                if(!StringUtils.isEmpty(param.getKeyword())){
                    String skuTitle = highlightFields.get("skuTitle").getFragments()[0].string();
                    model.setSkuTitle(skuTitle);
                }
                return model;
            }).collect(Collectors.toList());
            result.setProducts(skuEsModels);
            List<EsSearchResult.CatalogVo> catalogVos=new ArrayList<>();
            Aggregations aggregations = response.getAggregations();
            ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
            final List<? extends Terms.Bucket> buckets = catalogAgg.getBuckets();
            for(Terms.Bucket bucket:buckets){
                EsSearchResult.CatalogVo catalogVo=new EsSearchResult.CatalogVo();
                Long key = (Long) bucket.getKey();
                catalogVo.setCatalogId(key);
                ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
                String name = (String) catalogNameAgg.getBuckets().get(0).getKey();
                catalogVo.setCatalogName(name);
                catalogVos.add(catalogVo);
            }
            result.setCatalogs(catalogVos);
            List<EsSearchResult.AttrVo> attrVos=new ArrayList<>();
            ParsedNested attr = aggregations.get("attr");
            Aggregations attrAggregations = attr.getAggregations();
            ParsedLongTerms attrIdAgg = attrAggregations.get("attr_id_agg");
            final List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
            for (Terms.Bucket bucket : attrIdAggBuckets) {
                EsSearchResult.AttrVo attrVo=new EsSearchResult.AttrVo();
                Long attrId = (Long) bucket.getKey();
                attrVo.setAttrId(attrId);
                ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");

                String name = (String) attrNameAgg.getBuckets().get(0).getKey();
                attrVo.setAttrName(name);
                ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
                List<String> values=new ArrayList<>();
                for (Terms.Bucket valueAggBucket : attrValueAgg.getBuckets()) {
                    String value = valueAggBucket.getKeyAsString();
                    values.add(value);
                }
                attrVo.setAttrValue(values);
                attrVos.add(attrVo);
            }
            result.setAttrs(attrVos);

            List<EsSearchResult.BrandVo> brandVos=null;
            ParsedLongTerms brandAgg = aggregations.get("brand_agg");
            brandVos=brandAgg.getBuckets().stream().map(bucket -> {
                Long brandId = (Long) bucket.getKey();
                EsSearchResult.BrandVo brandVo=new EsSearchResult.BrandVo();
                brandVo.setBrandId(brandId);
                ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
                String img = brandImgAgg.getBuckets().get(0).getKeyAsString();
                brandVo.setBrandImg(img);
                ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
                String name=brandNameAgg.getBuckets().get(0).getKeyAsString();
                brandVo.setBrandName(name);
                return brandVo;
            }).collect(Collectors.toList());
            result.setBrands(brandVos);

            //总记录数
            long total=response.getHits().getTotalHits().value;
            result.setTotal(total);
            //页码
            Integer pageNum= param.getPageNum();
            if(pageNum==null){
                pageNum=1;
            }
            result.setPageNum(pageNum);
            int totalPageNum= (int) (total/EsConstant.PRODUCT_PAGESIZE);
            if(total%EsConstant.PRODUCT_PAGESIZE!=0){
                totalPageNum++;
            }
            result.setTotalPages(totalPageNum);
            List<Integer> pages=new ArrayList<>();
            for(int i=1;i<=totalPageNum;++i){
                pages.add(i);
            }
            result.setPageNavs(pages);

            //构建 nav 面包屑导航
            List<EsSearchResult.NavVo> navVos=new ArrayList<>();
            if(param.getAttrs()!=null && param.getAttrs().size()>0){
                navVos=param.getAttrs().stream().map((item)->{
                    EsSearchResult.NavVo navVo=new EsSearchResult.NavVo();
                    String[] s = item.split("_");
                    R r = productFeignService.attrInfo(Long.parseLong(s[0]));
                    result.getAttrIds().add(Long.parseLong(s[0]));
                    if(r.getCode()==0){
                        AttrResponseVo attrResponseVo =r.getData("attr", new TypeReference<AttrResponseVo>() {
                        });
                        navVo.setNavName(attrResponseVo.getAttrName());
                        navVo.setNavValue(s[1]);
                        String link=param.get_queryString();
                        String key="attrs="+s[0];
                        link = getLink(link, key);
                        navVo.setLink(link);
                    }
                    return navVo;
                }).collect(Collectors.toList());
            }
            if(param.getBrandId()!=null && param.getBrandId().size()>0){
                //TODO 远程查询brand
                R r=productFeignService.brandsInfoByIds(param.getBrandId());
                if(r.getCode()==0){
                    List<BrandVo> brands = r.getData("brand", new TypeReference<List<BrandVo>>(){});
                    EsSearchResult.NavVo navVo=new EsSearchResult.NavVo();
                    StringBuffer buffer=new StringBuffer();
                    String link=param.get_queryString();
                    for (BrandVo brand : brands) {
                        buffer.append(brand.getBrandName().toString()+";");
                        link=getLink(link,"brandId"+"="+brand.getBrandId());
                    }
                    navVo.setNavValue(buffer.toString());
                    navVo.setNavName("品牌");
                    navVo.setLink(link);
                    navVos.add(navVo);
                }
            }
            //TODO 分类
            result.setNavs(navVos);
        }

        return result;
    }

    private String getLink(String link, String key) {
        String q="";
        int i=0;
        boolean f=false;
        for(; i<= link.length()- key.length(); ++i){
            boolean flag=true;
            for(int k = 0; k< key.length() && flag; ++k){
                if(link.charAt(i+k)!= key.charAt(k)){
                    flag=false;
                }
            }
            if(flag){
                if(i>0 && link.charAt(i-1)=='&'){
                    q="&";
                }else{
                    f=true;
                }
                break;
            }
        }
        for(; i< link.length() && link.charAt(i)!='&'; ++i){
            q=q+ link.charAt(i);
        }
        if(f){
            q=q+"&";
        }
        link = link.replace(q,"");
        link = "http://search.mall.com/list.html?" + link;
        return link;
    }


    private SearchRequest buildSearchRequest(SearchParam param) {
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if(!StringUtils.isEmpty(param.getKeyword())){
            //模糊查询
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
            //highlight
            HighlightBuilder highlightBuilder=new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }
        //filter 三级分类
        if(param.getCatalog3Id()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));
        }
        //filter brand
        if(param.getBrandId()!=null && param.getBrandId().size()>0){
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
        }
        //filter nested attrsList
        if(param.getAttrs()!=null && param.getAttrs().size()>0){
            for(String attrVo:param.getAttrs()){
                String[] s = attrVo.split("_");
                String[] attrValues = s[1].split(":");
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                boolQuery.must(QueryBuilders.termQuery("attrs.attrId",s[0]));
                boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None));
            }
        }
        //filter hasStock
        if(param.getHasStock()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",param.getHasStock()==1));
        }
        //filter price range
        if(!StringUtils.isEmpty(param.getSkuPrice())){
            String[] s = param.getSkuPrice().split("_");
            if(s.length>1){
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").gte(s[0]).lte(s[1]));
            }else if(param.getSkuPrice().charAt(0)=='_'){
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").lte(s[0]));
            }else{
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("skuPrice").gte(s[0]));
            }
        }
        sourceBuilder.query(boolQueryBuilder);
        //sort
        if(!StringUtils.isEmpty(param.getSort())){
            String[] s = param.getSort().split("_");
            SortOrder order=(s[1].equals("desc")?SortOrder.DESC:SortOrder.ASC);
            sourceBuilder.sort(s[0],order);
        }
        int f=0;
        if(param.getPageNum()!=null && param.getPageNum()>0){
            f=param.getPageNum()-1;
        }
        f=f* EsConstant.PRODUCT_PAGESIZE;
        sourceBuilder.from(f);
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);

        //聚合分析
        TermsAggregationBuilder aggs = AggregationBuilders.terms("brand_agg").field("brandId").size(50);
        TermsAggregationBuilder brandName=AggregationBuilders.terms("brand_name_agg").field("brandName");
        brandName.size(1);
        TermsAggregationBuilder brandImgAgg = AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1);
        aggs.subAggregation(brandName);
        aggs.subAggregation(brandImgAgg);
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg");
        catalogAgg.field("catalogId").size(1);
        TermsAggregationBuilder catalogNameAgg = AggregationBuilders.terms("catalog_name_agg");
        catalogNameAgg.field("catalogName").size(1);
        catalogAgg.subAggregation(catalogNameAgg);
        NestedAggregationBuilder nested = AggregationBuilders.nested("attr", "attrs");
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg");
        attrIdAgg.field("attrs.attrId");
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attr_name_agg");
        attrNameAgg.field("attrs.attrName").size(1);
        attrIdAgg.subAggregation(attrNameAgg);
        TermsAggregationBuilder attrValueAgg = AggregationBuilders.terms("attr_value_agg");
        attrValueAgg.field("attrs.attrValue").size(50);
        attrIdAgg.subAggregation(attrValueAgg);
        nested.subAggregation(attrIdAgg);
        sourceBuilder.aggregation(aggs);
        sourceBuilder.aggregation(catalogAgg);
        sourceBuilder.aggregation(nested);
        SearchRequest request=new SearchRequest();
        request.indices(EsConstant.PRODUCT_INDEX);
        request.source(sourceBuilder);


        String s=sourceBuilder.toString();
        System.out.println("构建的DSL:\n"+s);

        return request;
    }
}
